#include "mmc.h"

#include "stm32f10x.h"

/* Definitions for MMC/SDC command */
#define CMD_GO_IDLE_STATE	        (0x40 + 0)	/* GO_IDLE_STATE */
#define CMD_SEND_OP_COND_MMC	    (0x40 + 1)	/* SEND_OP_COND (MMC) */
#define CMD_SEND_OP_COND_SDC	    (0xC0 + 41)	/* SEND_OP_COND (SDC) */
#define CMD_SEND_IF_COND	        (0x40 + 8)	/* SEND_IF_COND */
#define CMD_SEND_CSD	            (0x40 + 9)	/* SEND_CSD */
#define CMD_SEND_CID	            (0x40 + 10)	/* SEND_CID */
#define CMD_STOP_TRANSMISSION	    (0x40 + 12)	/* STOP_TRANSMISSION */
#define CMD_SD_STATUS	            (0xC0 + 13)	/* SD_STATUS (SDC) */
#define CMD_SET_BLOCKLEN	        (0x40 + 16)	/* SET_BLOCKLEN */
#define CMD_READ_SINGLE_BLOCK	    (0x40 + 17)	/* READ_SINGLE_BLOCK */
#define CMD_READ_MULTIPLE_BLOCK	    (0x40 + 18)	/* READ_MULTIPLE_BLOCK */
#define CMD_SET_BLOCK_COUNT	        (0x40 + 23)	/* SET_BLOCK_COUNT (MMC) */
#define CMD_SET_WR_BLK_ERASE_COUNT	(0xC0 + 23)	/* SET_WR_BLK_ERASE_COUNT (SDC) */
#define CMD_WRITE_BLOCK	            (0x40 + 24)	/* WRITE_BLOCK */
#define CMD_WRITE_MULTIPLE_BLOCK	(0x40 + 25)	/* WRITE_MULTIPLE_BLOCK */
#define CMD_APP_CMD	                (0x40 + 55)	/* APP_CMD */
#define CMD_READ_OCR	            (0x40 + 58)	/* READ_OCR */

/*uint8_t SPI_Put(uint8_t Data)
{
    while (!(SPI1->SR & SPI_SR_TXE)) {}              
    SPI1->DR = Data;    
    while (!(SPI1->SR & SPI_SR_RXNE)) {}    
    return SPI1->DR;
}

void SD_Select2(void)
{
    (GPIOB->BSRR |= (1 << (7 + 16)));
}

void SD_Deselect2(void)
{
    (GPIOB->BSRR |= (1 << 7));
}*/

#ifndef __TINY_TEST__
extern void SD_Select(void);
extern void SD_Deselect(void);
extern char SD_Put(char Data);
#else
void SD_Select(void)
{
}

void SD_Deselect(void)
{
}

char SD_Put(char Data)
{
    return 0;
}
#endif

/*#define SELECT()	    (GPIOB->BSRR |= (1 << (7 + 16)))
#define DESELECT()		(GPIOB->BSRR |= (1 << 7))*/

/*#define SELECT()        (GPIOA->BSRR |= (1 << (4 + 16)))
#define DESELECT()      (GPIOA->BSRR |= (1 << 4))*/

static volatile DSTATUS Stat = STA_NOINIT;
static BYTE CardType;

#define xmit_spi(dat)   SD_Put(dat)

static BYTE rcvr_spi(void)
{
	return xmit_spi(0xFF);
}

#define rcvr_spi_m(dst)	    *(dst) = xmit_spi(0xFF)

static BYTE wait_ready(void)
{
	BYTE res;
	rcvr_spi();
	
    do
    {
		res = rcvr_spi();
    }
	while (res != 0xFF);
	
    return res;
}

static void release_spi(void)
{
	SD_Deselect();
	rcvr_spi();
}

static BOOL rcvr_datablock (BYTE * buff, UINT btr)
{
	BYTE token;
	
    do 
    {
		token = rcvr_spi();
	} 
    while (token == 0xFF);


	if(token != 0xFE) 
        return FALSE;

	do 
    {
		rcvr_spi_m(buff++);
		rcvr_spi_m(buff++);
		rcvr_spi_m(buff++);
		rcvr_spi_m(buff++);
	} 
    while (btr -= 4);
	
    rcvr_spi();
	rcvr_spi();

	return TRUE;
}

static BOOL xmit_datablock (const BYTE * buff, BYTE token)
{
	BYTE resp, wc;
    
	if (wait_ready() != 0xFF) 
        return FALSE;

	xmit_spi(token);
	if (token != 0xFD) 
    {
		wc = 0;
		do 
        {			
			xmit_spi(*buff++);
			xmit_spi(*buff++);
		} 
        while (--wc);
		
        xmit_spi(0xFF);
		xmit_spi(0xFF);		
        resp = rcvr_spi();
        
		if ((resp & 0x1F) != 0x05)
			return FALSE;
	}

	return TRUE;
}

static BYTE send_cmd (BYTE cmd,	DWORD arg)
{
	BYTE n, res;
    
	if (cmd & 0x80) 
    {
		cmd &= 0x7F;
		res = send_cmd(CMD_APP_CMD, 0);
		if (res > 1) return res;
	}

	SD_Deselect();
	SD_Select();
	if (wait_ready() != 0xFF) 
        return 0xFF;    

	xmit_spi(cmd);
	xmit_spi((BYTE)(arg >> 24));
	xmit_spi((BYTE)(arg >> 16));
	xmit_spi((BYTE)(arg >> 8));
	xmit_spi((BYTE)arg);
	n = 0x01;
    
	if (cmd == CMD_GO_IDLE_STATE) 
        n = 0x95;
	
    if (cmd == CMD_SEND_IF_COND) 
        n = 0x87;
    
	xmit_spi(n);

	if (cmd == CMD_STOP_TRANSMISSION) 
        rcvr_spi();
	
    n = 10;	    
    do
		res = rcvr_spi();
	while (res & 0x80 && --n);

	return res;
}

DSTATUS MMC_disk_initialize (BYTE drv)
{
	BYTE n, cmd, ty, ocr[4];    
	
    if (Stat & STA_NODISK) 
        return Stat;    

	for (n = 10; n; n--) 
        rcvr_spi();    
	
    ty = 0;
    
	if (send_cmd(CMD_GO_IDLE_STATE, 0) == 1)
    {			        
		if (send_cmd(CMD_SEND_IF_COND, 0x1AA) == 1) 
        {	/* SDHC */            
			for (n = 0; n < 4; n++) 
                ocr[n] = rcvr_spi();
            
			if (ocr[2] == 0x01 && ocr[3] == 0xAA) 
            {
				while (send_cmd(CMD_SEND_OP_COND_SDC, 1UL << 30));

				if (send_cmd(CMD_READ_OCR, 0) == 0) 
                {
					for (n = 0; n < 4; n++) ocr[n] = rcvr_spi();
					ty = (ocr[0] & 0x40) ? CT_SD2 | CT_BLOCK : CT_SD2;
				}
			}
		} 
        else 
        {
			if (send_cmd(CMD_SEND_OP_COND_SDC, 0) <= 1) 
            {
				ty = CT_SD1; 
                cmd = CMD_SEND_OP_COND_SDC;
			} 
            else 
            {
				ty = CT_MMC; 
                cmd = CMD_SEND_OP_COND_MMC;
			}
			
            while (send_cmd(cmd, 0));

			if (send_cmd(CMD_SET_BLOCKLEN, 512) != 0)
				ty = 0;
		}
	}
	
    CardType = ty;
	release_spi();

	if (ty) 
		Stat &= ~STA_NOINIT;


	return Stat;
}

DSTATUS MMC_disk_status (BYTE drv)
{
	return Stat;
}

DRESULT MMC_disk_read (BYTE drv, BYTE * buff, DWORD sector, BYTE count)
{
	if (drv || !count) 
        return RES_PARERR;
	if (Stat & STA_NOINIT) 
        return RES_NOTRDY;

	if (!(CardType & CT_BLOCK)) 
        sector *= 512;

	if (count == 1) 
    {
		if ((send_cmd(CMD_READ_SINGLE_BLOCK, sector) == 0) && rcvr_datablock(buff, 512))
			count = 0;
	}
	else 
    {
		if (send_cmd(CMD_READ_MULTIPLE_BLOCK, sector) == 0) 
        {
			do 
            {
				if (!rcvr_datablock(buff, 512)) 
                    break;
				buff += 512;
			} while (--count);
			send_cmd(CMD_STOP_TRANSMISSION, 0);
		}
	}
	release_spi();
	return count ? RES_ERROR : RES_OK;
}

DRESULT MMC_disk_write (BYTE drv, const BYTE * buff, DWORD sector, BYTE count)
{
	if (Stat & STA_NOINIT) 
        return RES_NOTRDY;
    
	if (Stat & STA_PROTECT) 
        return RES_WRPRT;

	if (!(CardType & CT_BLOCK)) 
        sector *= 512;

	if (count == 1) 
    {
		if ((send_cmd(CMD_WRITE_BLOCK, sector) == 0) && xmit_datablock(buff, 0xFE))
			count = 0;
	}
	else 
    {
		if (CardType & CT_SDC) 
            send_cmd(CMD_SET_WR_BLK_ERASE_COUNT, count);
		if (send_cmd(CMD_WRITE_MULTIPLE_BLOCK, sector) == 0) 
        {
			do 
            {
				if (!xmit_datablock(buff, 0xFC)) 
                    break;
				buff += 512;
			} while (--count);
			
            if (!xmit_datablock(0, 0xFD))
				count = 1;
		}
	}
	release_spi();

	return count ? RES_ERROR : RES_OK;
}

DRESULT MMC_disk_ioctl(BYTE drv, BYTE ctrl, void * buff)
{
	DRESULT res;
	BYTE n, csd[16], *ptr = buff;
	WORD csize;

	res = RES_ERROR;

	if (ctrl == CTRL_POWER) 
    {
		switch (*ptr) 
        {
            case 0:
            {
                res = RES_OK;
                break;
            }
            
            case 1:
            {                
                res = RES_OK;
                break;
            }
            
            case 2:
            {                
                res = RES_OK;
                break;
            }
            
            default:
            {
                res = RES_PARERR;
                break;
            }
		}
	}
	else 
    {
		if (Stat & STA_NOINIT) 
            return RES_NOTRDY;

		switch (ctrl) 
        {
            case CTRL_SYNC:
            {
                SD_Select();
                if (wait_ready() == 0xFF)
                    res = RES_OK;
                break;
            }

            case GET_SECTOR_COUNT:
            {
                if ((send_cmd(CMD_SEND_CSD, 0) == 0) && rcvr_datablock(csd, 16)) 
                {
                    if ((csd[0] >> 6) == 1) 
                    {
                        csize = csd[9] + ((WORD)csd[8] << 8) + 1;
                        *(DWORD*)buff = (DWORD)csize << 10;
                    } 
                    else 
                    {
                        n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2;
                        csize = (csd[8] >> 6) + ((WORD)csd[7] << 2) + ((WORD)(csd[6] & 3) << 10) + 1;
                        *(DWORD*)buff = (DWORD)csize << (n - 9);
                    }
                    res = RES_OK;
                }
                break;
            }

            case GET_SECTOR_SIZE:
            {
                *(WORD*)buff = 512;
                res = RES_OK;
                break;
            }

            case GET_BLOCK_SIZE:
            {
                if (CardType & CT_SD2) 
                {
                    if (send_cmd(CMD_SD_STATUS, 0) == 0) 
                    {
                        rcvr_spi();
                        if (rcvr_datablock(csd, 16)) 
                        {
                            for (n = 64 - 16; n; n--) 
                                rcvr_spi();
                            *(DWORD*)buff = 16UL << (csd[10] >> 4);
                            res = RES_OK;
                        }
                    }
                } else 
                {
                    if ((send_cmd(CMD_SEND_CSD, 0) == 0) && rcvr_datablock(csd, 16)) 
                    {
                        if (CardType & CT_SD1) 
                        {
                            *(DWORD*)buff = (((csd[10] & 63) << 1) + ((WORD)(csd[11] & 128) >> 7) + 1) << ((csd[13] >> 6) - 1);
                        } 
                        else 
                        {
                            *(DWORD*)buff = ((WORD)((csd[10] & 124) >> 2) + 1) * (((csd[11] & 3) << 3) + ((csd[11] & 224) >> 5) + 1);
                        }
                        res = RES_OK;
                    }
                }
                break;
            }

            case MMC_GET_TYPE:
            {
                *ptr = CardType;
                res = RES_OK;
                break;
            }

            case MMC_GET_CSD:
            {
                if (send_cmd(CMD_SEND_CSD, 0) == 0 && rcvr_datablock(ptr, 16))
                    res = RES_OK;
                break;
            }

            case MMC_GET_CID:
            {
                if (send_cmd(CMD_SEND_CID, 0) == 0	&& rcvr_datablock(ptr, 16))
                    res = RES_OK;
                break;
            }

            case MMC_GET_OCR:
            {
                if (send_cmd(CMD_READ_OCR, 0) == 0) 
                {
                    for (n = 4; n; n--) 
                        *ptr++ = rcvr_spi();
                    res = RES_OK;
                }
                break;
            }

            case MMC_GET_SDSTAT:
            {
                if (send_cmd(CMD_SD_STATUS, 0) == 0) 
                {
                    rcvr_spi();
                    if (rcvr_datablock(ptr, 64))
                        res = RES_OK;
                }
                break;
            }

            default:
            {
                res = RES_PARERR;
                break;
            }
		}

		release_spi();
	}

	return res;
}
